package cn.kgc.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.kgc.base.BaseInfoProperties;
import cn.kgc.entity.*;
import cn.kgc.feign.UserFeignClient;
import cn.kgc.form.ResumeUpdateForm;
import cn.kgc.mapper.ResumeMapper;
import cn.kgc.service.*;
import cn.kgc.utils.PagedGridResult;
import cn.kgc.utils.RedisUtil;
import cn.kgc.vo.ResumeVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * @author YC
 */
@Slf4j
@Service("resumeService")
public class ResumeServiceImpl extends ServiceImpl<ResumeMapper, Resume> implements ResumeService {

    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    @Resource
    private UserFeignClient userFeignClient;
    @Resource
    private ResumeWorkExpService resumeWorkExpService;
    @Resource
    private ResumeEducationService resumeEducationService;
    @Resource
    private ResumeExpectService resumeExpectService;
    @Resource
    private ResumeCollectService resumeCollectService;
    @Resource
    private ResumeProjectExpService resumeProjectExpService;
    @Resource
    private ResumeReadService resumeReadService;
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Override
    public ResumeVO queryResumeVo(String resumeId) {
        // 判断缓存中是否有数据，有数据直接返回
        Object object = redisUtil.get(BaseInfoProperties.REDIS_RESUME_INFO + ":" + resumeId);
        if (object != null) {
            return (ResumeVO) object;
        }
        // 没数据，获得分布式锁的请求向下执行，没有获得锁的请求尝试重新获得锁（超过2s直接返回空信息）
        RLock lock = redissonClient.getLock(BaseInfoProperties.REDIS_RESUME_LOCK + ":" + resumeId);
        try {
            ResumeVO resumeVO = new ResumeVO();
            if (!lock.tryLock(2, 10, TimeUnit.SECONDS)) {
                return resumeVO;
            }
            // 获得锁的请求再次查询缓存中是否有数据，有数据返回
            object = redisUtil.get(BaseInfoProperties.REDIS_RESUME_INFO + ":" + resumeId);
            if (object != null) {
                return (ResumeVO) object;
            }
            // 仍然没数据，获得锁的请求查询数据库，将查询到的信息放入缓存中指定过期时间（数据库也没查到缓存中放入空数据）
            ResumeVO resumeAllInfo = getResumeAllInfo(resumeId);
            redisUtil.set(BaseInfoProperties.REDIS_RESUME_INFO + ":" + resumeId, resumeAllInfo,
                    7, TimeUnit.DAYS);
            return resumeAllInfo;
        } catch (InterruptedException e) {
            log.error("查询简历信息失败", e);
            return null;
        } finally {
            // 如果线程获得锁，无论如何最后释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    private ResumeVO getResumeAllInfo(String resumeId) {
        ResumeVO resumeVO = new ResumeVO();
        Resume resume = this.getById(resumeId);
        if (resume == null) {
            redisUtil.set(BaseInfoProperties.REDIS_RESUME_INFO + ":" + resumeId, resumeVO,
                    60, TimeUnit.SECONDS);
            return resumeVO;
        }
        resumeVO.setId(resume.getUserId());
        BeanUtil.copyProperties(resume, resumeVO);
        // 线程池异步编排操作
        CompletableFuture<Void> resumeBaseInfoTask = CompletableFuture.runAsync(() -> {
            Object userObject = userFeignClient.detail(resume.getUserId()).getData();
            Users users = JSONUtil.toBean(JSONUtil.toJsonStr(userObject), Users.class);
            resumeVO.setUserName(users.getShowWhichName() == 1 ? users.getNickname() : users.getRealName());
            resumeVO.setAge(users.getAge());
            resumeVO.setSex(users.getSex() == 2 ? "保密" : users.getSex() == 1 ? "男" : "女");
        }, threadPoolTaskExecutor);
        CompletableFuture<Void> resumeEducationTask = CompletableFuture.runAsync(() -> {
            resumeVO.setResumeEducations(resumeEducationService.list(
                    new LambdaQueryWrapper<ResumeEducation>()
                            .eq(ResumeEducation::getResumeId, resumeId)
            ));
        }, threadPoolTaskExecutor);
        CompletableFuture<Void> resumeExpectTask = CompletableFuture.runAsync(() -> {
            resumeVO.setResumeExpects(resumeExpectService.list(
                    new LambdaQueryWrapper<ResumeExpect>()
                            .eq(ResumeExpect::getResumeId, resumeId)
            ));
        }, threadPoolTaskExecutor);
        CompletableFuture<Void> resumeWorkExpTask = CompletableFuture.runAsync(() -> {
            resumeVO.setResumeWorks(resumeWorkExpService.list(
                    new LambdaQueryWrapper<ResumeWorkExp>()
                            .eq(ResumeWorkExp::getResumeId, resumeId)
            ));
        }, threadPoolTaskExecutor);
        CompletableFuture<Void> resumeProjectExpTask = CompletableFuture.runAsync(() -> {
            resumeVO.setResumeProjectExps(resumeProjectExpService.list(
                    new LambdaQueryWrapper<ResumeProjectExp>()
                            .eq(ResumeProjectExp::getResumeId, resumeId)
            ));
        }, threadPoolTaskExecutor);
        CompletableFuture.allOf(
                resumeBaseInfoTask, resumeEducationTask, resumeExpectTask,
                resumeWorkExpTask, resumeProjectExpTask).join();
        return resumeVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean initResume(String userId) {
        Resume resume = Resume.builder().userId(userId).build();
        return baseMapper.insert(resume) > 0;
    }

    @Override
    public boolean updateResume(ResumeUpdateForm form) {
        return this.update(
                new LambdaUpdateWrapper<Resume>()
                        .eq(Resume::getUserId, form.getId())
                        .set(Resume::getAdvantage, form.getAdvantage())
                        .set(Resume::getAdvantageHtml, form.getAdvantageHtml())
                        .set(Resume::getCredentials, form.getCredentials())
                        .set(Resume::getSkills, form.getSkills())
                        .set(Resume::getStatus, form.getStatus())
        );
    }

    @Override
    public void refreshResume(String resumeId, String userId) {
        String key = BaseInfoProperties.REDIS_RESUME_INFO + ":" + userId;
        // 用户简历信息变化，删除缓存中该用户的简历信息
        if (redisUtil.hasKey(key)) {
            redisUtil.delete(key);
        }
        //根据用户的ID获得用户的信息
        Object object = userFeignClient.detail(userId).getData();
        Users users = JSONUtil.toBean(JSONUtil.toJsonStr(object), Users.class);
        // 将用户的简历信息封装到Eo中，再将Eo对象保存到ES中
        ResumeEo resumeEo = new ResumeEo();
        resumeEo.setBirthday(DateUtil.format(users.getBirthday(), "yyyy-MM-dd"));
        resumeEo.setAge(users.getAge());
        resumeEo.setUserId(users.getId());
        resumeEo.setNickName(users.getNickname());
        resumeEo.setSex(users.getSex());
        resumeEo.setFace(users.getFace());
        int workYears = users.getStartWorkDate() == null ? 0 : DateUtil.ageOfNow(users.getStartWorkDate());
        resumeEo.setWorkYears(workYears);
        // 工作相关
        List<ResumeWorkExp> workExps = resumeWorkExpService.queryResumeWorkExpByResumeId(resumeId, userId);
        if (CollUtil.isNotEmpty(workExps)) {
            ResumeWorkExp workExp = workExps.get(0);
            resumeEo.setCompanyName(workExp.getCompanyName());
            resumeEo.setPosition(workExp.getPosition());
            resumeEo.setIndustry(workExp.getIndustry());
        }
        // 教育相关
        List<ResumeEducation> educations = resumeEducationService.queryResumeEducationByResumeId(resumeId, userId);
        if (CollUtil.isNotEmpty(educations)) {
            ResumeEducation education = educations.get(0);
            resumeEo.setEduction(education.getEducation());
            resumeEo.setSchool(education.getSchool());
            resumeEo.setMajor(education.getMajor());
        }
        // 查询简历信息
        Resume resume = baseMapper.selectOne(
                new LambdaQueryWrapper<Resume>()
                        .eq(Resume::getId, resumeId)
                        .eq(Resume::getUserId, userId)
        );
        // 其他字段
        resumeEo.setCredentials(resume.getCredentials());
        resumeEo.setSkills(resume.getSkills());
        resumeEo.setAdvantage(resume.getAdvantage());
        resumeEo.setAdvantageHTML(resume.getAdvantageHtml());
        resumeEo.setJobStatus(resume.getStatus());
        // 简历刷新时间
        resumeEo.setRefreshTime(DateUtil.format(resume.getRefreshTime(), "yyyy-MM-dd HH:mm:ss"));
        // 查询求职期望
        List<ResumeExpect> expectList = resumeExpectService.queryResumeExpectByResumeId(resumeId, userId);
        List<ResumeEo> resumeEos = new ArrayList<>();
        if (CollUtil.isNotEmpty(expectList)) {
            expectList.forEach(expect -> {
                resumeEo.setResumeExpectId(expect.getId());
                resumeEo.setJobType(expect.getJobName());
                resumeEo.setBeginSalary(expect.getBeginSalary());
                resumeEo.setEndSalary(expect.getEndSalary());
                resumeEo.setCity(expect.getCity());
                // 设置简历的最新收藏时间
                ResumeCollect resumeCollect = resumeCollectService.queryResumeCollect(expect.getId());
                if (resumeCollect != null) {
                    resumeEo.setHrCollectResumeTime(
                            DateUtil.format(resumeCollect.getCreatedTime(), "yyyy-MM-dd HH:mm:ss"));
                }
                // TODO 设置简历的最新阅读时间
                resumeEos.add(resumeEo);
            });
        }
        // 保存在ES中
        IndexQuery indexQuery = new IndexQueryBuilder().withObject(resumeEos).build();
        // 指定ES中的索引名称
        IndexCoordinates coordinates = IndexCoordinates.of("resume");
        elasticsearchRestTemplate.save(indexQuery, coordinates);
    }

    @Override
    public PagedGridResult pageResume(ResumeSearchForm form) {
        // 构建复合查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 标识是否传入查询条件
        boolean flag = false;
        // 昵称 优势 技能点 证书
        if (StrUtil.isNotBlank(form.getBasicTitle())) {
            flag = true;
            BoolQueryBuilder basicQueryBuilder = QueryBuilders.boolQuery()
                    .should(QueryBuilders.matchQuery("nickName", form.getBasicTitle()))
                    .should(QueryBuilders.matchQuery("advantage", form.getBasicTitle()))
                    .should(QueryBuilders.matchQuery("skills", form.getBasicTitle()))
                    .should(QueryBuilders.matchQuery("credentials", form.getBasicTitle()));
            // 将查询条件添加到复合查询中
            boolQueryBuilder.must(basicQueryBuilder);
        }
        // 求职类型
        if (StrUtil.isNotBlank(form.getJobType())) {
            flag = true;
            boolQueryBuilder.must(QueryBuilders.matchQuery("jobType", form.getJobType()));
        }
        // 年龄
        if (form.getBeginAge() > 0 && form.getEndAge() > 0) {
            flag = true;
            boolQueryBuilder.must(QueryBuilders.rangeQuery("age")
                    .gte(form.getBeginAge()).lte(form.getEndAge()));
        }
        // 性别
        if (form.getSex() == 1 || form.getSex() == 2) {
            flag = true;
            boolQueryBuilder.must(QueryBuilders.matchQuery("sex", form.getSex()));
        }
        // 最近活跃度
        if (form.getActiveTimes() > 0) {
            flag = true;
            // ES中的refreshTime字段比较最近三天活跃： 当前时间 >=  (当前时间 - 最近三天活跃时间)（单位ms） >=  刷新时间
            Date date = new Date(System.currentTimeMillis() - form.getActiveTimes());
            boolQueryBuilder.must(QueryBuilders.rangeQuery("refreshTime")
                    .gte(date.getTime()));
        }
        // 工作经验
        if (form.getBeginWorkExpYears() > 0 && form.getEndWorkExpYears() > 0) {
            flag = true;
            boolQueryBuilder.must(QueryBuilders.rangeQuery("workExpYears")
                    .gte(form.getBeginWorkExpYears()).lte(form.getEndWorkExpYears()));
        }
        // 薪水范围
        // 当前不允许HR不填最高或不填最低，只需满足 求职者最低 <= HR最高 并且 求职者最高 >= HR最低 即可
        // 如果允许HR不填最高或不填最低时，可以使用多重if判断
        if (form.getBeginSalary() > 0 && form.getEndSalary() > 0) {
            flag = true;
            BoolQueryBuilder salaryBool = QueryBuilders.boolQuery();
            // 求职者最低 <= HR最高
            salaryBool.filter(QueryBuilders.rangeQuery("beginSalary").lte(form.getEndSalary()));
            // 求职者最高 >= HR最低
            salaryBool.filter(QueryBuilders.rangeQuery("endSalary").gte(form.getBeginSalary()));
            boolQueryBuilder.filter(salaryBool);
        }
        // 如果有查询条件使用查询条件，如果没有查询条件查询全部
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        if (flag) {
            nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
        } else {
            nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
        }
        // 设置分页（ES的分页从0开始，当前页-1）
        int pageIndex = form.getPageIndex() <= 0 || form.getPageIndex() > 30 ? 1 : form.getPageIndex();
        Pageable pageable = PageRequest.of(pageIndex - 1, form.getPageSize());
        // 查询ES
        Query query = nativeSearchQueryBuilder.withPageable(pageable).build();
        SearchHits<ResumeEo> searchHis = elasticsearchRestTemplate.search(query, ResumeEo.class);
        // 按照ES的返回值获取实际的需要的数据
        List<ResumeEo> resumeEos = searchHis.getSearchHits().stream()
                .map(SearchHit::getContent)
                .collect(Collectors.toList());
        // 封装查询到的信息
        PagedGridResult pagedGridResult = new PagedGridResult();
        pagedGridResult.setPage(pageIndex);
        pagedGridResult.setPageSize(form.getPageSize());
        pagedGridResult.setRows(resumeEos);
        pagedGridResult.setRecords(searchHis.getTotalHits());
        return pagedGridResult;
    }

}